package org.codetranslators.compiler.mips.translate;

import java.util.Vector;
import java.util.HashMap;

import org.codetranslators.compiler.tiger.semantics.Level;

public class AssemblyConstructor 
{
	public static final String tab = "\t";
	public static final String newLine = "\r\n";
	
	public static final String CALL_PRELUDE = "jal ";
	
	private int numRegistersUsed;
	private ActivationFrame activationFrame;
	private int maxOutgoingParams;
	private Vector functionBodyCode;
	private HashMap fNameVsNumRegsUsed;
	private int numFunctionsCalled;
	
	public AssemblyConstructor(int numRegistersUsed, Level level,
			Vector functionBodyCode, HashMap fNameVsNumRegsUsed)
	{
		this.numRegistersUsed = numRegistersUsed;
		this.maxOutgoingParams = level.getMaxOutgoingParams();
		this.activationFrame = level.getActivationFrame();
		this.numFunctionsCalled = level.getNumFunctionsCalled();
		this.functionBodyCode = functionBodyCode;
		this.fNameVsNumRegsUsed = fNameVsNumRegsUsed;
	}
	
	public Vector createCodeForFunction()
	{
		Vector functionCode = new Vector();
		
		activationFrame.setNumRegistersUsed(numRegistersUsed);
		activationFrame.setMaxOutgoingParams(maxOutgoingParams);
		
		// If this function calls other functions, insert the pre and post call instructions;
		// else, retain original body code.
		Vector bodyCodeWithPreAndPostCallInstrs = functionBodyCode;
		if(numFunctionsCalled > 0)   
			bodyCodeWithPreAndPostCallInstrs = getCodeWithPreAndPostCallInstrs(functionBodyCode);
		
		// Add the prologue and epilogue for this function
		functionCode.addAll(bodyCodeWithPreAndPostCallInstrs);
		Vector prologue = activationFrame.getPrologueInstructions();
		functionCode.addAll(1, prologue);
		Vector epilogue = activationFrame.getEpilogueInstructions();
		functionCode.addAll(epilogue);
		
		return functionCode;
	}
	
	public Vector getCodeWithPreAndPostCallInstrs(Vector functionBody)
	{
		Vector code = new Vector();
		
		// Loop through the function body, looking for function calls
		int prevCallIndex = 0;
		boolean callFound = false;
		for(int i = 0; i < functionBody.size(); i++)
		{
			String assemblyInstruction = (String) functionBody.elementAt(i);
			String callInstr = assemblyInstruction.trim();
			if(callInstr.startsWith(CALL_PRELUDE))
			{
				callFound = true;
				String nameOfCalledFunction = getFunctionName(callInstr);
				String numRegsUsedStr = (String)fNameVsNumRegsUsed.get(nameOfCalledFunction);
				
				int numRegistersUsedInCalledFunc = 0;
				if(numRegsUsedStr != null) // Could be null if the function is from a library
					numRegistersUsedInCalledFunc = Integer.parseInt(numRegsUsedStr);
				
				Vector [] preAndPostCallInstrs = getPreAndPostCallInstrs(numRegistersUsedInCalledFunc);
				
				code.addAll(functionBody.subList(prevCallIndex, i)); // Add all code from prev. to call
				code.addAll(preAndPostCallInstrs[0]);                // Add the precall instructions
				code.addElement(assemblyInstruction);          // Add the call instruction itself
				code.addAll(preAndPostCallInstrs[1]);            // Add the postcall instructions
				
				prevCallIndex = i+1;
			}
		}
		if(callFound)
		{
			// Attach remaining piece of code
			code.addAll(functionBody.subList(prevCallIndex, functionBody.size()));
		}
		else
		{
			// This function does not call any functions; return the original vector
			code = functionBody;
		}
		
		return code;
		
	}
	
	public Vector[] getPreAndPostCallInstrs(int numRegistersUsedInCalledFunc)
	{
		Vector[] preAndPostCallInstrs = new Vector[2];
		Vector preCallInstrs = new Vector();
		Vector postCallInstrs = new Vector();
		
		if(numRegistersUsed > ActivationFrame.NUM_CALLEE_SAVED_REGISTERS
				&& numRegistersUsedInCalledFunc > ActivationFrame.NUM_CALLEE_SAVED_REGISTERS)
		{
			// Both the caller and the callee make use of caller-saved registers; therefore, it is necessary
			// to store those that the caller uses
			int numCallerSavedRegsUsed = numRegistersUsed - ActivationFrame.NUM_CALLEE_SAVED_REGISTERS;
			int offSetFromSP =  1                                            // Static link
							   + maxOutgoingParams                           // max Outgoing params
							   + ActivationFrame.NUM_CALLEE_SAVED_REGISTERS  // Callee-saved registers
							   + 1;   										 // Start of caller-saved registers
			int offSetInBytes = offSetFromSP * ActivationFrame.WORDSIZE;
			
			for(int i = 0; i < numCallerSavedRegsUsed; i++)
			{
				String reg = "$t" + i;
				
				String saveInstr = tab + "sw " + reg + ", " + offSetInBytes + "($SP)" + newLine;
				preCallInstrs.addElement(saveInstr);
				
				String loadInstr = tab + "lw " + reg + ", " + offSetInBytes + "($SP)"  + newLine;
				postCallInstrs.addElement(loadInstr);
			}
		}
		else
		{
			// Nothing to save; just display a comment
			String comment = tab + "# PRE-CALL -> NO REGISTER NEEDS TO BE SAVED"  + newLine;
			preCallInstrs.add(comment);
		}
		
		preAndPostCallInstrs[0] = preCallInstrs;
		preAndPostCallInstrs[1] = postCallInstrs;
		
		return preAndPostCallInstrs;
	}
	
	private String getFunctionName(String callInstr)
	{
		int preludeLength = CALL_PRELUDE.length();
		return callInstr.substring(preludeLength, callInstr.length()).trim();
	}
}
